/*
 * This file is part of Espruino, a JavaScript interpreter for Microcontrollers
 *
 * Copyright (C) 2017 Gordon Williams <gw@pur3.co.uk>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * ----------------------------------------------------------------------------
 * ESP32 specific Bluetooth utils
 * ----------------------------------------------------------------------------
 */


#include "BLE/esp32_bluetooth_utils.h"
#include "BLE/esp32_gap_func.h"
#include "BLE/esp32_gatts_func.h"
#include "BLE/esp32_gattc_func.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "freertos/FreeRTOS.h"
#include "jsvariterator.h"
#include "jsinteractive.h"

esp_ble_debug_t bleEventDebug = 0;

esp_err_t initController(){
  esp_err_t ret;
  esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    ret = esp_bt_controller_init(&bt_cfg);if(ret) {jsWarn("init controller failed:%x\n",ret); return ret;}
  ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);if(ret) {jsWarn("enable controller failed:%x\n",ret); return ret;}
  return ret;
}
esp_err_t initBluedroid(){
  esp_err_t ret;
  ret = esp_bluedroid_init();if (ret) {jsWarn("init bluedroid failed:%x\n",ret);return ret;}
  ret = esp_bluedroid_enable();if (ret) {jsWarn("enable bluedroid failed:%x\n",ret);return ret;}
  return ret;
}
esp_err_t deinitController(){
  esp_err_t ret;
  ret = esp_bt_controller_disable(); if (ret) {jsWarn("disable bluetooth failed:%x\n"); return ret;}
  ret = esp_bt_controller_deinit(); if (ret) {jsWarn("deinit bluetooth failed:%x\n"); return ret;}
  return ret;
}
esp_err_t deinitBluedroid(){
  esp_err_t ret;
  ret = esp_bluedroid_disable();if (ret) {jsWarn("disable bluedroid failed:%x\n",ret);return ret;}
  ret = esp_bluedroid_deinit();if (ret) {jsWarn("deinit bluedroid failed:%x\n",ret);return ret;}
  return ret;
}
esp_err_t registerCallbacks(){
  esp_err_t ret;
  ret = esp_ble_gap_register_callback(gap_event_handler);if (ret){jsWarn("gap register error:%x\n", ret);return ret;}
  ret = esp_ble_gatts_register_callback(gatts_event_handler);if(ret){jsWarn("gatts register error:%x\n", ret);return ret;}
  ret = esp_ble_gattc_register_callback(gattc_event_handler);if(ret){jsWarn("gattc regigister error:%x\n",ret);return ret;}
  return ret;
}
esp_err_t setMtu(){
  esp_err_t ret;
  ret = esp_ble_gatt_set_local_mtu(500);if(ret)jsWarn("set local MTU failed:%x\n",ret);
  return ret;
}

void ESP32_setBLE_Debug(int level){
  bleEventDebug = level;
}
static char *gattsEvent2String(esp_gatts_cb_event_t event){
  switch(event){
    case 0: return "REG";
    case 1: return "READ";
    case 2: return "WRITE";
    case 3: return "EXEC_WRITE";
    case 4: return "MTU";
    case 5: return "CONF";
    case 6: return "UNREG";
    case 7: return "CREATE";
    case 8: return "ADD_INCL_SRVC";
    case 9: return "ADD_CHAR";
    case 10: return "ADD_CHAR_DESCR";
    case 11: return "DELETE";
    case 12: return "START";
    case 13: return "STOP";
    case 14: return "CONNECT";
    case 15: return "DISCONNECT";
    case 16: return "OPEN";
    case 17: return "CANCEL_OPEN";
    case 18: return "CLOSE";
    case 19: return "LISTEN";
    case 20: return "CONGEST";
    case 21: return "RESPONSE";
    case 22: return "CREAT_ATTR_TAB";
    case 23: return "CREAT_ATTR_TAB";
  }
  return "unknown GattsEvent";
}
static char *gattcEvent2String(esp_gattc_cb_event_t event){
  switch(event){
    case 0: return "REG";
    case 1: return "UNREG";
    case 2: return "OPEN";
    case 3: return "READ_CHAR";
    case 4: return "WRITE_CHAR";
    case 5: return "CLOSE";
    case 6: return "SEARCH_CMPL";
    case 7: return "SEARCH_RES";
    case 8: return "READ_DESCR";
    case 9: return "WRITE_DESCR";
    case 10: return "NOTIFY";
    case 11: return "PREP_WRITE";
    case 12: return "EXEC";
    case 13: return "ACL";
    case 14: return "CANCEL_OPEN";
    case 15: return "SRVC_CHG";
    case 17: return "ENC_CMPL_CB";
    case 18: return "CFG_MTU";
    case 19: return "ADV_DATA";
    case 20: return "MULT_ADV_ENB";
    case 21: return "ADV_ADV_UPD";
    case 22: return "MULT_ADV_DATA";
    case 23: return "MULT_ADV_DIS";
    case 24: return "CONGEST";
    case 25: return "BTH_SCAN_ENB";
    case 26: return "BTH_SCAN_CFG";
    case 27: return "BTH_SCAN_RD";
    case 28: return "BTH_SCAN_THR";
    case 29: return "BTH_SCAN_PARAM";
    case 30: return "BTH_SCAN_DIS";
    case 31: return "SCAN_FLT_CFG";
    case 32: return "SCAN_FLT_PARAM";
    case 33: return "SCAN_FLT_STATUS";
    case 34: return "ADV_VSC";
    case 38: return "REG_FOR_NOTIFY";
    case 39: return "UNREG_FOR_NOTIFY";
    case 40: return "CONNECT";
    case 41: return "DISCONNECT";
    case 42: return "READ_MUTIPLE";
    case 43: return "QUEUE_FULL";
  }
  return "unknown GattcEvent";
}
static char *gapEvent2String(esp_gap_ble_cb_event_t event){
  switch(event){
    case 0: return "ADV_DATA_SET_COMPLETE";
    case 1: return "SCAN_RSP_DATA_SET_COMPLETE";
    case 2: return "SCAN_PARAM_SET_COMPLETE";
    case 3: return "SCAN_RESULT";
    case 4: return "ADV_DATA_RAW_SET_COMPLETE";
    case 5: return "SCAN_RSP_DATA_RAW_SET_COMPLETE";
    case 6: return "ADV_START_COMPLETE";
    case 7: return "SCAN_START_COMPLETE";
    case 8: return "AUTH_CMPL";
    case 9: return "KEY";
    case 10: return "SEC_REQ";
    case 11: return "PASSKEY_NOTIF";
    case 12: return "PASSKEY_REQ";
    case 13: return "OOB_REQ";
    case 14: return "LOCAL_IR";
    case 15: return "LOCAL_ER";
    case 16: return "NC_REQ";
    case 17: return "ADV_STOP_COMPLETE";
    case 18: return "SCAN_STOP_COMPLETE";
    case 19: return "SET_STATIC_RAND_ADDR";
    case 20: return "UPDATE_CONN_PARAMS";
    case 21: return "SET_PKT_LENGTH_COMPLETE";
    case 22: return "SET_LOCAL_PRIVACY_COMPLETE";
    case 23: return "REMOVE_BOND_DEV_COMPLETE";
    case 24: return "CLEAR_BOND_DEV_COMPLETE";
    case 25: return "GET_BOND_DEV_COMPLETE_EVT";
    case 26: return "READ_RSSI_COMPLETE";
    case 27: return "UPDATE_WHITELIST_COMPLETE";
  }
  return "unknown GapEvent";
}

void jsWarnGattsEvent(esp_gatts_cb_event_t event,esp_gatt_if_t gatts_if){
  if(bleEventDebug & ESP_BLE_DEBUG_GATTS)
    jsiConsolePrintf("Event:ESP_GATTS_%s_EVT gatts_if:%d\n",gattsEvent2String(event), gatts_if);
}
void jsWarnGattcEvent(esp_gattc_cb_event_t event,esp_gatt_if_t gattc_if){
  if(bleEventDebug & ESP_BLE_DEBUG_GATTC)
    jsiConsolePrintf("Event:ESP_GATTC_%s_EVT gattc_if:%d\n",gattcEvent2String(event), gattc_if);
}
void jsWarnGapEvent(esp_gap_ble_cb_event_t event){
  if(bleEventDebug & ESP_BLE_DEBUG_GAP)
    jsiConsolePrintf("Event:ESP_GAP_BLE_%s_EVT\n",gapEvent2String(event));
}

void jsWarnUUID(esp_bt_uuid_t char_uuid){
  if (char_uuid.len == ESP_UUID_LEN_16) {
    jsWarn("- - - Char UUID16: %x", char_uuid.uuid.uuid16);
  } else if (char_uuid.len == ESP_UUID_LEN_32) {
    jsWarn("- - - Char UUID32: %x", char_uuid.uuid.uuid32);
  } else if (char_uuid.len == ESP_UUID_LEN_128) {
    jsWarn("- - - Char UUID128: %x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x", char_uuid.uuid.uuid128[0],
      char_uuid.uuid.uuid128[1], char_uuid.uuid.uuid128[2], char_uuid.uuid.uuid128[3],
      char_uuid.uuid.uuid128[4], char_uuid.uuid.uuid128[5], char_uuid.uuid.uuid128[6],
      char_uuid.uuid.uuid128[7], char_uuid.uuid.uuid128[8], char_uuid.uuid.uuid128[9],
      char_uuid.uuid.uuid128[10], char_uuid.uuid.uuid128[11], char_uuid.uuid.uuid128[12],
      char_uuid.uuid.uuid128[13], char_uuid.uuid.uuid128[14], char_uuid.uuid.uuid128[15]);
  } else {
    jsWarn("- - - Char UNKNOWN LEN %d\n", char_uuid.len);
  }
}
void jsWarnBDA(uint8_t *bda){
  jsWarn("bda %02x:%02x:%02x:%02x:%02x:%02x\n",bda[0],bda[1],bda[2],bda[3],bda[4],bda[5]);
}
void jsWarnHeap(char * whereAmI){
  jsWarn("%s Heap:%d, jsVars:%d\n",whereAmI,esp_get_free_heap_size(),jsvGetMemoryUsage());
}

void bleGetHiddenName(char *eventName, char *hiddenName, uint16_t pos){
  strcpy(eventName,hiddenName);
  itostr(pos,&eventName[strlen(eventName)],16);
}
bool bleRemoveChild(JsVar *parent, JsVar *blevar){
  bool ret = false;
  JsvObjectIterator it;
  jsvObjectIteratorNew(&it, parent);
  while (jsvObjectIteratorHasValue(&it)) {
    JsVar *child = jsvObjectIteratorGetKey(&it);
    JsVar *name = jsvNewFromStringVar(child, 0, 10);
    if(jsvIsEqual(name,blevar)){
      jsvRemoveChild(parent,child);
      ret = true;
    }
    jsvUnLock2(child, name);
    jsvObjectIteratorNext(&it);
  }
  jsvObjectIteratorFree(&it);
  return ret;
}
void bleRemoveChilds(JsVar *parent){
  JsVar *blevar = jsvNewFromString(BLE_CHAR_VALUE);
  while(bleRemoveChild(parent,blevar)){}
  jsvUnLock(blevar);
}

void bleuuid_TO_espbtuuid(ble_uuid_t ble_uuid, esp_bt_uuid_t *esp_uuid){
  switch(ble_uuid.type){
    case BLE_UUID_TYPE_UNKNOWN:
      jsError("empty UUID type\n");
      break;
    case BLE_UUID_TYPE_BLE:
      esp_uuid->len = ESP_UUID_LEN_16;
      esp_uuid->uuid.uuid16 = ble_uuid.uuid;
      break;
    case BLE_UUID_TYPE_128:
      esp_uuid->len = ESP_UUID_LEN_128;
      for(int i = 0; i < 16; i++){
        esp_uuid->uuid.uuid128[i] = ble_uuid.uuid128[i];
      }
      break;
    default:
      jsError("bleuuid_TO_espbtuuid unknown UUID TYPE\n");
  }
}

void espbtuuid_TO_bleuuid(esp_bt_uuid_t esp_uuid, ble_uuid_t *ble_uuid) {
  switch(esp_uuid.len) {
    case ESP_UUID_LEN_16:
      ble_uuid->type = BLE_UUID_TYPE_BLE;
      ble_uuid->uuid = esp_uuid.uuid.uuid16;
      break;
    case ESP_UUID_LEN_128:
      ble_uuid->type = BLE_UUID_TYPE_128;
      for(int i = 0; i < 16; i++)
        ble_uuid->uuid128[i] = esp_uuid.uuid.uuid128[i];
      break;
    default:
      jsError("espbtuuid_TO_bleuuid unknown UUID TYPE\n");
  }
}

void bleuuid_To_uuid128(ble_uuid_t ble_uuid, uint8_t *ble128){
  uint8_t tmp[] = {0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00};
  switch (ble_uuid.type){
    case BLE_UUID_TYPE_UNKNOWN:
      jsError("empty UUID type\n");
      break;
    case BLE_UUID_TYPE_BLE:
      for(int i = 0; i < 16; i++){
        ble128[i] = tmp[i];
      }
      ble128[12] = ble_uuid.uuid & 0xff;
      ble128[13] = ble_uuid.uuid >> 8;
      break;
    case BLE_UUID_TYPE_128:
      for(int i = 0; i < 16; i++){
        ble128[i] = ble_uuid.uuid128[i];
      }
      break;
    default:
      jsError("unknown UUID type\n");
  }
}

bool espbtuuid_equal(esp_bt_uuid_t a, esp_bt_uuid_t b) {
  return
      (a.len == ESP_UUID_LEN_16 && b.len == ESP_UUID_LEN_16 && a.uuid.uuid16 == b.uuid.uuid16) ||
      (a.len == ESP_UUID_LEN_32 && b.len == ESP_UUID_LEN_32 && a.uuid.uuid32 == b.uuid.uuid32) ||
      (a.len == ESP_UUID_LEN_128 && b.len == ESP_UUID_LEN_128 && memcmp(a.uuid.uuid128, b.uuid.uuid128, ESP_UUID_LEN_128)==0);
}

void bleaddr_TO_espbtaddr(ble_gap_addr_t ble_addr, esp_bd_addr_t remote_bda, esp_ble_addr_type_t *remote_bda_type) {
  // reverse bytes in address and copy
  for(int i=0;i<6;i++)
    remote_bda[5-i] = ble_addr.addr[i];
  // BLE_GAP_ADDR_TYPES and esp_ble_addr_type_t match (no need for conversion)
  *remote_bda_type = ble_addr.addr_type;
}

void espbtaddr_TO_bleaddr(esp_bd_addr_t remote_bda, esp_ble_addr_type_t remote_bda_type, ble_gap_addr_t *ble_addr) {
  // reverse bytes in address and copy
  for(int i=0;i<6;i++)
    ble_addr->addr[i] = remote_bda[5-i];
  // BLE_GAP_ADDR_TYPES and esp_ble_addr_type_t match (no need for conversion)
  ble_addr->addr_type = remote_bda_type;
}
